package org.linlinjava.litemall.core.exception;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.linlinjava.litemall.core.util.ResponseUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.annotation.Order;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.Locale;
import java.util.Set;

@ControllerAdvice
@Order
@Slf4j
public class GlobalExceptionHandler {

    private Log logger = LogFactory.getLog(GlobalExceptionHandler.class);

    /**
     * 异常消息文件错误前缀
     */
    public static final String PREFIX_MESSAGE_ERROR = "ERROR_";
    /**
     * 响应头错误类型标识
     */
    public static final String KEY_RESPONSE_ERROR_TYPE = "ERROR_TYPE";

    @Autowired
    private MessageSource messageSource;

    private String buildErrorMessage(AbstractCoderException ex) {
        Locale locale = LocaleContextHolder.getLocale();

        return messageSource.getMessage(StrUtil.addPrefixIfNot(String.valueOf(ex.getErrorCode().getCode()), PREFIX_MESSAGE_ERROR)
                , null, ex.getMessage(), locale);
    }

    /**
     * 框架自定义异常
     */
    @ExceptionHandler(value = {BusinessException.class})
    @ResponseBody
    public Result<?> businessExceptionHandler(HttpServletResponse servletResponse, BusinessException ex) {
        if (CollUtil.isNotEmpty(ex.getAttributes())) {
            log.error("BIZ_ERROR[{}]:{}\tattributes:{}", ex.getErrorCode().getCode(), ex.getMessage(), ex.getAttributes());
        } else {
            log.error("BIZ_ERROR[{}]:{}", ex.getErrorCode().getCode(), ex.getMessage());
        }
        writeErrorType(servletResponse, ex.getErrorType());
        Result<Object> response = new Result(Boolean.FALSE, ex.getErrorCode().getCode(), buildErrorMessage(ex));
        if (CollectionUtil.isNotEmpty(ex.getAttributes())) {
            response.setData(ex.getAttributes());
        }
        return response;
    }

    private void writeErrorType(HttpServletResponse servletResponse, int errorType) {
        servletResponse.setHeader(KEY_RESPONSE_ERROR_TYPE, String.valueOf(errorType));
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseBody
    public Object badArgumentHandler(IllegalArgumentException e) {
        logger.error(e.getMessage(), e);
        return ResponseUtil.badArgumentValue();
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseBody
    public Object badArgumentHandler(MethodArgumentTypeMismatchException e) {
        logger.error(e.getMessage(), e);
        return ResponseUtil.badArgumentValue();
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseBody
    public Object badArgumentHandler(MissingServletRequestParameterException e) {
        logger.error(e.getMessage(), e);
        return ResponseUtil.badArgumentValue();
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    public Object badArgumentHandler(HttpMessageNotReadableException e) {
        logger.error(e.getMessage(), e);
        return ResponseUtil.badArgumentValue();
    }

    @ExceptionHandler(ValidationException.class)
    @ResponseBody
    public Object badArgumentHandler(ValidationException e) {
        logger.error(e.getMessage(), e);
        if (e instanceof ConstraintViolationException) {
            ConstraintViolationException exs = (ConstraintViolationException) e;
            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            for (ConstraintViolation<?> item : violations) {
                String message = ((PathImpl) item.getPropertyPath()).getLeafNode().getName() + item.getMessage();
                return ResponseUtil.fail(402, message);
            }
        }
        return ResponseUtil.badArgumentValue();
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Object seriousHandler(HttpServletResponse servletResponse, Exception ex) {
        if (null != ex.getCause() && ex.getCause() instanceof BusinessException) {
            log.warn("Wrapped business exception : {}", ex.getClass().getName());
            return businessExceptionHandler(servletResponse, (BusinessException) ex.getCause());
        }
        logger.error(ex.getMessage(), ex);
        return ResponseUtil.serious();
    }

}
